######################################################
# PyMailGui 1.0 - A Python/Tkinter email client.
#
# Adds a Tkinter-based GUI interface to the pymail
# script's functionality.  Works for POP/SMTP based
# email accounts using sockets on the machine on 
# which this script is run. 
#
# I like this script for two main reasons:
# 1) It's scriptable--you control its evolution
# from this point forward, and can easily customize
# and program it by editing the Python code in this
# file, unlike canned products like MS-Outlook.
# 2) It's portable--this script can be used to 
# process your email on any machine with internet
# sockets where Python and Tkinter are installed; 
# use the simple command-line based pymail.py if 
# you have sockets and Python but not Tkinter.
# E.g., I use this to read my pop email account
# from UNIX machines when I'm away from home PC.
#
# Caveats:
# - Only fetches new mail on 'Load' button for speed,
#   but that assumes nothing else is changing the pop 
#   mailbox while the gui is running.  No other 
#   email tools (or PyMailGui instances) can delete 
#   mail off the pop server while gui runs, else mail
#   numbers displayed are invalid.  Note that the email
#   box is locked during deletes too: incoming mail won't
#   invalidate numbers, since added to end when unlocked.
# - Run me from command line or file explorer to watch 
#   my printed messages (not visible from launchers). 
# - On a Save, the file select dialog pops up a 
#   warning about replacing an existing file if one is
#   chosen, even though messages are simply appended 
#   ('a' mode) to the existing file; this is a Tk 
#   issue (Tkinter calls TK's tk_getSaveFile command). 
# - Save also doesn't remember the directory where a 
#   file was last saved (You need to renavigate from the
#   examples root); to rememeber the last selected dir,
#   make and save a tkFileDialog.SaveAs class object,
#   and call its show( ) method to get a filename,
#   instead of calling tkFileDialog.asksaveasfilename.
#   Change: this program now implements this scheme.
#
# On long-running callbacks (load, send, delete):
# Handles load/send/delete wait states by running the
# blocking call in a non-gui thread, and posting a
# modal dialog box during the wait (else the GUI is
# hung while the blocking call runs).  The main 
# thread creates windows and process all gui events,
# and the non-gui thread does the long-running 
# non-gui call and signals completion on exit. 
# The main thread uses a busy widget.update loop
# to process events during the wait instead of
# wait_variable(), because the non-gui thread 
# can't set tk variables in Windows either.
#
# A prior version tried to update the gui during the
# wait by starting a thread that woke up N time per 
# sec to call rootWin.update(); this fails badly on 
# Windows.  Apparently, the rule on Windows is that
# only the thread that creates a window can handle
# events for it; hence, new threads can't call for
# widget.update() to update the gui (or do other GUI
# work), as that call triggers gui processing in the
# main thread.  Worse, threads can't set Tk variables
# reliably either (this goes to the main thread too),
# so I use simple shared global variables and busy 
# wait loops here to signal thread exit, rather than
# Tk vars + widget.wait_variable(); the latter is 
# easier (the Tk event loop is active during a wait), 
# but fails with Windows threads.  thread module 
# locks and threading module conditions/joins may
# have been used to signal thread exit too. 
#
# Note that this is a Windows issue (not Tk), and it
# is okay to spawn new threads under Tk as long as they
# are strictly non-gui.  Also note that in principle, 
# we could run both a load and send thread at the same
# time (ex: write new email while loading); instead,
# the main GUI Box is disabled while any load or
# save threads runs, to avoid more mutex issues.
# Finally, we can also implement a wait state with
# the tk widget.after(msec, func) call, scheduling
# func to check the thread exit var every N msecs
# (see other examples for use), rather than a local
# widget.update() event dispatch loop as done here. 
# func could destroy the popup window on thread exit.
# That requires some recoding though, as callers now
# assume that busy states return after thread exit.
#
# There is much room for improvement and new features
# here--left as exercises.  Example extensions: 
# - Add an automatic spam filter, which matches 
#   from/to hdrs etc. with a regex and auto deletes
#   matching messages as they are being downloaded;
# - Do attachments: auto decoding/unpacking for 
#   multi-part emails, etc.: see decode*.py here
#   and module mimetools for attachment hints;
# - Fields in the main list box could be padded to 
#   a standard length or put in distinct widgets.
# - Make me a class to avoid global vars, and make
#   it easier to attach this GUI to another one;
#   that would also allow creation of more than one
#   mail client gui per process, but this won't work
#   as currently designed (deletes in one gui can
#   invalidate others, due to new pop msg numbers); 
# - Inherit from GuiMaker here to get menu/toolbars;
# - As is, always requests the user's pop account 
#   password initially, and after load errors; in
#   principle, this program could instead save the 
#   user's password in a file on the client machine, 
#   perhaps encrypted (see PyMailCgi for encryption),
#   and use the file if present instead of asking.
#   I decided against this because I wanted to avoid 
#   security issues completely (on machines shared by
#   multiple people, anyone can see and decrypt a file) 
# - Don't delete the edit window if a send fails, 
#   else all the text is lost [done--4/00].
# - Write a remote CGI-based file tool, which does
#   pop/smtp too, but runs on a server and interacts
#   with users by generating html pages instead of
#   running a GUI on the local client machine--see 
#   example PyMailCgi in the CGI section of the book.
#   This approach is limited in functionality, but 
#   Python has to be installed on the server only.
######################################################
